﻿#region Copyright Notice
// 
// 
// Copyright (c) 2009-2010 Anindya Chatterjee
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// 
// 
// 
#endregion
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;
using Event.Plugin.Configuration;
using Event.Plugin.Utility;
using Timer=System.Windows.Forms.Timer;

namespace Event.Plugin
{
    /// <summary>
    /// Abstract event class, contains the common functionalities.
    /// Calling StartListening method will enable a timer, which will call EventLogic method
    /// after a regular interval. The EventLogic method (implemented in a subclass), will raise the
    /// Listened event, which will intern call the notify method and which will notify the user about
    /// the event capture. The EndListening method will stop the timer. ListeningInterval will change
    /// the timer interval for each event differently.
    /// </summary>
    public abstract class AbstractEvent : IEvent
    {
        // This will store the ids of the event, randomly generated
        private static readonly List<int> IdStore = new List<int>();
        // Id Random number generator
        private static readonly Random Seed = new Random();
        private readonly Timer _timer;
        private ICondition _condition;
        private IEventHost _host;
        private volatile bool _listened;
        private BackgroundWorker _workerThread;
        
        /// <summary>
        /// Initializes a new instance of the <see cref="AbstractEvent"/> class.
        /// </summary>
        protected AbstractEvent()
        {
            _timer = new Timer {Enabled = false, Interval = 1000};
            _timer.Tick += TimerTick;

            // if the generated id is already there, then regenarate it or else store it so that each event has unique id
            RandomGen:
            int id = Seed.Next();
            if (IdStore.Contains(id))
                goto RandomGen;
            IdStore.Add(id);
            Id = id;
            
            // executes event specific initialization routines
            InitializeEvent();
        }

        /// <summary>
        /// Asleeps the event listening for a given interval of time.
        /// </summary>
        /// <param name="interval">an interval for which the listening will be asleep</param>
        protected void Sleep(int interval)
        {
            // to aviod cross thread call, just thread sleep is the best choice here.
            Thread.Sleep(interval);
        }

        #region IEvent Members

        /// <summary>
        /// Gives access to the event specific settings
        /// </summary>
        public IEventSettings EventSettings { get; set; }

        /// <summary>
        /// Signaled the listener that listening is completed
        /// </summary>
        public event Signal Listened;

        /// <summary>
        /// Indicates if an event is scheduled or not
        /// </summary>
        public bool IsScheduled
        {
            get { return (Condition != null); }
        }

        /// <summary>
        /// Call it when to notify that the event is captured
        /// </summary>
        public abstract void Notify();

        /// <summary>
        /// Put your business logic here which will execute after every ListeningInterval of time
        /// </summary>
        public abstract void EventLogic();

        /// <summary>
        /// The condition is to be satisfied to run a scheduled event
        /// </summary>
        public virtual ICondition Condition
        {
            get { return _condition; }
            set
            {
                _condition = value;
                _condition.Parent = this;
            }
        }

        /// <summary>
        /// Notifier handle for the event
        /// </summary>
        public virtual INotifier UserNotifier { get; set; }

        /// <summary>
        /// Name of the event
        /// </summary>
        public virtual string Name { get; set; }

        /// <summary>
        /// The time interval for which EventLogic will be executed
        /// </summary>
        public virtual int ListeningInterval
        {
            get { return _timer.Interval; }
            set { _timer.Interval = value; }
        }

        /// <summary>
        /// Starts listening the EventLogic
        /// </summary>
        public virtual void StartListening()
        {
            _timer.Enabled = true;
        }

        /// <summary>
        /// True if the condition is satisfied for a scheduled event, otherwise false
        /// </summary>
        /// <returns></returns>
        public virtual bool IsConditionSatisfied()
        {
            if (Condition == null)
                return true;
            return Condition.Statisfied;
        }

        /// <summary>
        /// Execute it after one listening is complete
        /// </summary>
        public virtual void EndListening()
        {
            _timer.Enabled = false;
        }

        /// <summary>
        /// Initializes settings and all, of an event
        /// </summary>
        public virtual void InitializeEvent()
        {
        }

        /// <summary>
        /// The event host for this event
        /// </summary>
        public IEventHost Host
        {
            get { return _host; }
            set
            {
                _host = value;
                _host.Register(this);
            }
        }

        /// <summary>
        /// Event id, it should be unique
        /// </summary>
        public int Id { get; set; }

        /// <summary>
        /// Indicates that an event must keep on listening after successful capture
        /// </summary>
        public bool Loop { get; set; }

        /// <summary>
        /// Gain access to the application settings
        /// </summary>
        public ISettings ApplicationSettings { get; set; }

        /// <summary>
        /// Handle any unhandled exception occurred in the event
        /// </summary>
        /// <value></value>
        public UnhandledException UnhandledExceptionHandler { get; set; }

        /// <summary>
        /// Signaled the listener that listening is completed
        /// </summary>
        /// <value></value>
        public bool ListeningComplete
        {
            get { return _listened; }
            set { _listened = value; }
        }

        #endregion

        /// <summary>
        /// Executes the main event logic after a regular interval of time
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void TimerTick(object sender, EventArgs e)
        {
            if (_workerThread == null)
            {
                _workerThread = new BackgroundWorker();
                _workerThread.DoWork += WorkerThreadDoWork;
                _workerThread.RunWorkerCompleted += WorkerThreadRunWorkerCompleted;
                _workerThread.RunWorkerAsync();
            }
            if (ListeningComplete)
            {
                ListeningComplete = false;
                Listened(this, new SignalArgs {Sender = this});
            }
        }

        private void WorkerThreadRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            _workerThread.Dispose();
            _workerThread = null;
        }

        private void WorkerThreadDoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
                EventLogic();
            }
            catch (Exception ex)
            {
                if (UnhandledExceptionHandler != null)
                    UnhandledExceptionHandler(this, ex);
                EndListening();
                if (Condition != null)
                {
                    Condition.EndListening();
                }
            }
        }
    }
}